import React, { createRef } from 'react';
import cx from 'classnames';
import PropTypes from 'prop-types';

import { frequency } from 'modules/RX';

import observeNode from './observeNode';

import './index.less';

import showHide from '@wpt/showHide';

/**
 * 监听scroll事件
 * @param {Function} callback
 * @param {Number} time
 * @returns
 */
function listenScroll(callback, time = 30) {
    const realFn = frequency(callback, time);
    window.addEventListener('scroll', realFn);

    return () => {
        window.removeEventListener('scroll', realFn);
    };
}

/**
 * 根绝相对定位元素，获取fixed top值
 * @param {*} selector
 * @param {*} dom
 * @returns
 */
function getTop(selector, dom) {
    const defaultTop = WPT.getTopNavHeight();
    // 返回默认值，对全屏webview需要特殊处理
    if (!selector) return defaultTop;

    // 对于指定选择器或者top的情况，需要自己手动添加 getTopNavHeight 虚拟导航高度
    // 数字
    if (/^-?\d+(.\d+)?$/.test(`${selector}`)) return Number(selector) + defaultTop;
    // 或者是一个选择器
    const relativeDom = $(dom).parents('#Page').find(selector)[0];
    if (relativeDom) {
        // const { top, height } = relativeDom.getBoundingClientRect();

        // // 获取到的数据可能是不稳定的，带有不稳定的小数
        // return Math.floor(top + height);

        const { height } = relativeDom.getBoundingClientRect();
        // 获取到的数据可能是不稳定的，带有不稳定的小数
        return Math.floor(height + defaultTop);
    }

    return defaultTop;
}

@showHide
export default class Sticky extends React.Component {
    /**
     * https://github.com/yahoo/react-stickynode
     * 0 STATUS_ORIGINAL The default status, located at the original position.
     * 1 STATUS_RELEASED The released status, located at somewhere on document, but not default one.
     * 2 STATUS_FIXED The sticky status, located fixed to the top or the bottom of screen.
     */
    static STATUS_FIXED = 2;

    static STATUS_ORIGINAL = 0;

    static defaultProps = {
        top: 0, // 参考高度 或者选择器
        onStateChange: () => 0, // 当fixed状态发生变换时触发
        holderClassName: '', // 占位元素class
        className: '', // 根元素class
        activeClassName: '', // fixed时class
        children: null,
        enabled: true, // 是否启用
    };

    static propTypes = {
        top: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
        className: PropTypes.string,
        holderClassName: PropTypes.string,
        activeClassName: PropTypes.string,
        onStateChange: PropTypes.func,
        children: PropTypes.node,
        enabled: PropTypes.bool,
    };

    state = {
        isFixed: false,
        top: 0,
    };

    holderRef = createRef();

    fixedRef = createRef();

    componentDidMount() {
        this.onPageShow();
    }

    componentDidUpdate(prevP) {
        if (prevP.enabled && !this.props.enabled) {
            this.onPageHide();
        } else if (!prevP.enabled && this.props.enabled) {
            this.onPageShow();
        }
    }

    componentWillUnmount() {
        this.onPageHide();
    }

    onPageShow() {
        this.remove = this.watch();
    }

    onPageHide() {
        this.remove && this.remove();
    }

    watch = () => {
        this.onPageHide();

        const { holderRef, fixedRef, props } = this;

        if (!props.enabled) {
            return null;
        }

        // 更新高度，保证内容和holder高度一致
        const updateHeight = () => {
            if (fixedRef.current && holderRef.current) {
                const height = fixedRef.current.clientHeight;
                holderRef.current.style.cssText += `;height: ${height}px;`;
            }
        };
        updateHeight();

        // 看起来像是因为page diaplay: none，导致安卓机上获取到的fixedRef height为0，做个延时处理，保证高度没有问题
        // 或者是因为webview一开始的尺寸为0x0，待确定本质原因
        setTimeout(updateHeight, 400);

        // 监听scroll事件
        const removeScroll = listenScroll(() => {
            if (holderRef.current) {
                const { top } = holderRef.current.getBoundingClientRect();
                const relativeTop = getTop(props.top, holderRef.current);
                // const isFixed = top <= relativeTop;
                const isFixed = top + 46 <= relativeTop; // wwm.20.09.15 + 46
                const { isFixed: prevFixed, top: prevTop } = this.state;
                if (prevFixed !== isFixed || prevTop !== relativeTop) {
                    this.setState({
                        isFixed,
                        top: relativeTop,
                    });

                    prevFixed !== isFixed &&
                        props.onStateChange &&
                        props.onStateChange({ status: isFixed ? Sticky.STATUS_FIXED : Sticky.STATUS_ORIGINAL });
                }
            }
        });

        return () => {
            removeScroll();
            observeNode(fixedRef.current, updateHeight);
        };
    };

    /**
     * 滚动到顶部临界位置
     * @memberof Sticky
     */
    scrollToTop = () => {
        if (!this.holderRef.current) return;
        // 要减去虚拟导航的高度，否则定位不准确
        const top = Math.min($(window).scrollTop(), $(this.holderRef.current).offset().top - WPT.getTopNavHeight());
        // 此处获取的top值有可能包含有小数，从而导致没有触发sticky的fixed逻辑
        // 顶部会有1px的空隙，可通过Math.ceil优化掉
        $(window).scrollTop(Math.ceil(top));
    };

    render() {
        const { holderClassName, className, activeClassName, children } = this.props;
        const { isFixed, top } = this.state;

        return (
            <div
                className={cx(
                    'sticky-outer-wrapper',
                    isFixed ? ['sticky-status-fixed', activeClassName] : 'sticky-status-static',
                    className
                )}
            >
                <div ref={this.holderRef} className={cx('sticky-holder', holderClassName)} />
                <div className={cx('sticky-inner-wrapper')} ref={this.fixedRef} style={{ top }}>
                    {children}
                </div>
            </div>
        );
    }
}
